package com.opensymphony.xwork2.interceptor;

import java.util.Iterator;
import java.util.List;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.ExceptionHolder;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

public class ExceptionMappingInterceptor extends AbstractInterceptor {
	protected static final Logger LOG = LoggerFactory
			.getLogger(ExceptionMappingInterceptor.class);
	protected Logger categoryLogger;
	protected boolean logEnabled;
	protected String logCategory;
	protected String logLevel;

	public ExceptionMappingInterceptor() {
		this.logEnabled = false;
	}

	public boolean isLogEnabled() {
		return this.logEnabled;
	}

	public void setLogEnabled(boolean logEnabled) {
		this.logEnabled = logEnabled;
	}

	public String getLogCategory() {
		return this.logCategory;
	}

	public void setLogCategory(String logCatgory) {
		this.logCategory = logCatgory;
	}

	public String getLogLevel() {
		return this.logLevel;
	}

	public void setLogLevel(String logLevel) {
		this.logLevel = logLevel;
	}

	public String intercept(ActionInvocation invocation) throws Exception {
		String result;
		try {
			result = invocation.invoke();
		} catch (Exception e) {
			e.printStackTrace();
			if (isLogEnabled())
				handleLogging(e);

			List exceptionMappings = invocation.getProxy().getConfig()
					.getExceptionMappings();
			String mappedResult = findResultFromExceptions(exceptionMappings, e);
			if (mappedResult != null) {
				result = mappedResult;
				publishException(invocation, new ExceptionHolder(e));
			} else {
				throw e;
			}
		}

		return result;
	}

	protected void handleLogging(Exception e) {
		if (this.logCategory != null) {
			if (this.categoryLogger == null) {
				this.categoryLogger = LoggerFactory.getLogger(this.logCategory);
			}
			doLog(this.categoryLogger, e);
		} else {
			doLog(LOG, e);
		}
	}

	protected void doLog(Logger logger, Exception e) {
		if (this.logLevel == null) {
			logger.debug(e.getMessage(), e, new String[0]);
			return;
		}

		if ("trace".equalsIgnoreCase(this.logLevel))
			logger.trace(e.getMessage(), e, new String[0]);
		else if ("debug".equalsIgnoreCase(this.logLevel))
			logger.debug(e.getMessage(), e, new String[0]);
		else if ("info".equalsIgnoreCase(this.logLevel))
			logger.info(e.getMessage(), e, new String[0]);
		else if ("warn".equalsIgnoreCase(this.logLevel))
			logger.warn(e.getMessage(), e, new String[0]);
		else if ("error".equalsIgnoreCase(this.logLevel))
			logger.error(e.getMessage(), e, new String[0]);
		else if ("fatal".equalsIgnoreCase(this.logLevel))
			logger.fatal(e.getMessage(), e, new String[0]);
		else
			throw new IllegalArgumentException("LogLevel [" + this.logLevel
					+ "] is not supported");
	}

	protected String findResultFromExceptions(
			List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
		String result = null;

		if (exceptionMappings != null) {
			int deepest = 2147483647;
			for (Iterator i$ = exceptionMappings.iterator(); i$.hasNext();) {
				Object exceptionMapping = (ExceptionMappingConfig) i$.next();
				ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
				int depth = getDepth(exceptionMappingConfig
						.getExceptionClassName(), t);
				if ((depth >= 0) && (depth < deepest)) {
					deepest = depth;
					result = exceptionMappingConfig.getResult();
				}
			}
		}

		return result;
	}

	public int getDepth(String exceptionMapping, Throwable t) {
		return getDepth(exceptionMapping, t.getClass(), 0);
	}

	private int getDepth(String exceptionMapping, Class exceptionClass,
			int depth) {
		if (exceptionClass.getName().contains(exceptionMapping)) {
			return depth;
		}

		if (exceptionClass.equals(Throwable.class))
			return -1;

		return getDepth(exceptionMapping, exceptionClass.getSuperclass(),
				depth + 1);
	}

	protected void publishException(ActionInvocation invocation,
			ExceptionHolder exceptionHolder) {
		invocation.getStack().push(exceptionHolder);
	}
}